/**
 * ...
 * @author Franco Ponticelli
 */

package uform.model.core;

import hxevents.Dispatcher;
import hxevents.Notifier;
import thx.error.Error;
import uform.model.core.Schema;
import thx.error.NullArgument;
import thx.util.Result;

using thx.util.Results;
using StringTools;

class Model
{
	static var _re = new EReg("^([a-z0-9_]+)(?:\\[(\\d+)\\])?$", "i");
	
	public var onChange(default, null) : Dispatcher<ModelChangeEvent>;
	public var onData(default, null) : Notifier;
	public var onSchema(default, null) : Notifier;
	public var data(default, setData) : Dynamic;
	public var schema(default, setSchema) : Schema;
	public var prefix : String;
	
	public var clearing(default, null) : Bool;
	
	public function new(?schema : Schema, ?data : Dynamic)
	{
		this.onChange = new Dispatcher();
		this.onData = new Notifier();
		this.onSchema = new Notifier();
		this.schema = schema;
		this.data = data;
		this.prefix = "";
		clearing = false;
	}
	
	function setData(data : Dynamic)
	{
		this.data = {};
		
		if(null != data)
		{
			if (!Reflect.isObject(data))
				throw "data is not an object";
		} else {
			clearing = true;
			data = {};
		}
		if(null != schema)
		{
			for(field in schema.fields())
			{
				var value = Reflect.field(data, field.name);
				var key = addPrefix(field.name);
				switch(validateAt(key, value))
				{
					case Ok:
						set(key, value);
					case Failure(e):
						set(key, null);
				}
			}
		}
		onData.dispatch();
		clearing = false;
		return this.data;
	}
	
	function setSchema(value : Schema)
	{
		schema = value;
		onSchema.dispatch();
		return value;
	}

	function removePrefix(key : String)
	{
		if (!key.startsWith(prefix))
			throw new Error("invalid key {0}", key);
		return key.substr(prefix.length);
	}
	
	function addPrefix(key : String)
	{
		return prefix + key;
	}
	
	public function resolve(key : String)
	{
		var pkey = removePrefix(key);
		var crawler = new Crawler(pkey, data);
		schema.resolve(crawler);
		return crawler;
	}
	
	public function setString(key : String, value : String)
	{
		var c = resolve(key);
		var v = c.handler.parse(value);
		c.set(v);
		onChange.dispatch( { path : key, value : v } );
	}
	
	public function set(key : String, value : Dynamic)// : Result
	{
		resolve(key).set(value);
		onChange.dispatch( { path : key, value : value } );
	}

	public function setStringIfValid(key : String, s : String)
	{
		try
		{
			var c = resolve(key);
			var value = c.handler.parse(s);
			switch(c.handler.validate(value))
			{
				case Ok:
					c.set(value);
					onChange.dispatch( { path : key, value : value } );
				case Failure(e):
					trace("validation failure at " + key + ": " + Std.string(e));
			};
	   } catch(e : Dynamic) {
	   		trace("exception: " + Std.string(e));
	   }
	}
	
	public function setIfValid(key : String, value : Dynamic)
	{
		try
		{
			var c = resolve(key);
			switch(c.handler.validate(value))
			{
				case Ok:
					c.set(value);
					onChange.dispatch( { path : key, value : value } );
				case Failure(e):
					trace("validation failure: " + Std.string(e));
			};
	   } catch(e : Dynamic) {
	   		trace("exception: " + Std.string(e));
	   }
	}

	public function validate()
	{
		return schema.validate(data);
	}
	
	public function isValid()
	{
		return switch(validate())
		{
			case Ok: true;
			case Failure(_): false;
		}
	}
	
	public function validateAt(key : String, value : Dynamic)
	{
		return resolve(key).handler.validate(value);
	}
	
	public function validateStringAt(key : String, value : Dynamic)
	{
		var h = resolve(key).handler;
		var v = h.parse(value);
		return h.validate(v);
	}
	
	public function parseAt(key : String, value : String)
	{
		return resolve(key).handler.parse(value);
	}
	
	public function get(key : String) : Dynamic
	{
		return resolve(key).get();
	}
	
	public function getString(key : String) : String
	{
		var c = resolve(key);
		return c.handler.render(c.get());
	}
	
	public function render()
	{
		return schema.render(data);
	}
}

typedef ModelChangeEvent = {
	path : String,
	value : Dynamic
}